package uk.co.creacog.ccglib.flex.controls.soundviz
{
	import flash.display.Bitmap;
	import flash.display.BitmapData;
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.filters.BlurFilter;
	import flash.filters.ColorMatrixFilter;
	import flash.geom.Point;
	import flash.media.SoundMixer;
	import flash.utils.ByteArray;
	
	import mx.core.UIComponent;
	import mx.logging.ILogger;
	import mx.logging.Log;
/*
The MIT License

Copyright (c) 2009 creative-cognition ltd

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
	

/**
 * Colour of the line representing the left channel
 * @default SimpleOscilloscope.DEFAULT_LEFT_COLOUR
 */
 	[ Style( name="leftColour", 	format="Color", type="uint", inherit="no") ]
/**
 * Colour of the line representing the right channel
 * @default SimpleOscilloscope.DEFAULT_RIGHT_COLOUR
 */
 	[ Style( name="rightColour", 	format="Color", type="uint", inherit="no") ]
 /**
 * Alpha value of the left channel
 * @default 1.0 SimpleOscilloscope.DEFAULT_LEFT_ALPHA
 */
 	[ Style( name="leftAlpha",  type="Number", inherit="no") ]
 /**
 * Alpha value of the right channel
 * @default 1.0 SimpleOscilloscope.DEFAULT_RIGHT_ALPHA
 */
 	[ Style( name="rightAlpha",  type="Number", inherit="no") ]
 /**	
 * Thickness of the lines representing the sound wave
 * @default SimpleOscilloscope.STYLE_WAVE_THICKNESS
 */
 	[ Style( name="waveThickness", 	format="Length", type="uint", inherit="no") ]
/**	
 * <p>
 * Separation beteen the two channel wave-plots in pixels. Note the overall height of the component
 * remains unchanged.
 * </p><p>
 * Normally the zero line of the two wave lines is the vertical centre of the rectangle.
 * This style intruduces a gap, pushing the lines apart. For example a value of 50
 * will move the left channel up 25 pixels and the right channel down by 25 pixels.
 * </p>
 * @default SimpleOscilloscope.DEFAULT_WAVE_SEPARATION
 */
 	[ Style( name="waveSeparation", type="Number", inherit="no") ]
/**
 * <p>
 * Wave 'draw mode'. String. Can be:
 * </p><p>
 * SimpleOscilloscope.WAVE_DRAW_LINE_ONLY<br/>
 * SimpleOscilloscope.WAVE_DRAW_FILLED<br/>
 * </p>
 * @default SimpleOscilloscope.WAVE_DRAW_LINE_ONLY
 */
 	[ Style( name="waveDrawMode", type="String", inherit="no", enumeration="drawLineOnly,drawFilled") ]
/**
 * <p>
 * Alpha multiplier applied as a filter behind the waveform. Range 0.0 to 1.0. A value less than one
 * effects a decay of the alpha each frame. A value of 0 (default) effectively turns off all the decay
 * styles effectively an instant decay. A value of 1.0 will accumulate pixels in the background
 * from the wave until it eventually fills in.
 * </p>
 * @default SimpleOscilloscope.DEFAULT_ALPHA_DECAY
 */
 	[ Style( name="alphaDecay", type="Number", inherit="no") ]
/**
 * <p>
 * Red multiplier applied as a filter behind the waveform. Only positive values make sense.
 * The red channel of the background filtered bitmap is multiplied by this value every frame.
 * This style will only take effect if there is the alphaDecay style has a value other than 0.
 * </p>
 * @see alphaDecay
 * @default SimpleOscilloscope.DEFAULT_RED_MULTIPLIER
 */
 	[ Style( name="redMultiplier", type="Number", inherit="no") ]
/**
 * <p>
 * Green multiplier applied as a filter behind the waveform. Only positive values make sense.
 * The green channel of the background filtered bitmap is multiplied by this value every frame.
 * This style will only take effect if there is the alphaDecay style has a value other than 0.
 * </p>
 * @see alphaDecay
 * @default SimpleOscilloscope.DEFAULT_GREEN_MULTIPLIER
 */
 	[ Style( name="greenMultiplier", type="Number", inherit="no") ]
/**
 * <p>
 * Blue multiplier applied as a filter behind the waveform. Only positive values make sense.
 * The blue channel of the background filtered bitmap is multiplied by this value every frame.
 * This style will only take effect if there is the alphaDecay style has a value other than 0.
 * </p>
 * @see alphaDecay
 * @default SimpleOscilloscope.DEFAULT_BLUE_MULTIPLIER
 */
 	[ Style( name="blueMultiplier", type="Number", inherit="no") ]
/**
 * <p>
 * Blur X. Horizontanl blurring applied to the background, behind the wave-plot, every frame.
 * Valid floating point numbers from 0-255.
 * This style will only take effect if there is the alphaDecay style has a value other than 0.
 * </p>
 * @see alphaDecay
 * @see flash.filters.BlurFilter
 * @default SimpleOscilloscope.DEFAULT_BLUR_X
 */
 	[ Style( name="blurX", type="Number", inherit="no") ]
/**
 * <p>
 * Blur Y. Vertical blurring applied to the background, behind the wave-plot, every frame.
 * Valid floating point numbers from 0-255.
 * This style will only take effect if there is the alphaDecay style has a value other than 0.
 * </p>
 * @see alphaDecay
 * @see flash.filters.BlurFilter
 * @default SimpleOscilloscope.DEFAULT_BLUR_Y
 */
 	[ Style( name="blurY", type="Number", inherit="no" ) ]
/**
 * <p>
 * Scroll X. Horizontal scrolling in pixels applied to the background, behind the wave-plot, every frame.
 * This style will only take effect if there is the alphaDecay style has a value other than 0.
 * </p>
 * @see alphaDecay
 * @see flash.display.BitmapData
 * @default SimpleOscilloscope.DEFAULT_SCROLL_X
 */
 	[ Style( name="scrollX", type="int", inherit="no" ) ]
/**
 * <p>
 * Scroll Y. Vertical scrolling in pixels applied to the background, behind the wave-plot, every frame.
 * This style will only take effect if there is the alphaDecay style has a value other than 0.
 * </p>
 * @see alphaDecay
 * @see flash.display.BitmapData
 * @default SimpleOscilloscope.DEFAULT_SCROLL_Y
 */
 	[ Style( name="scrollY", type="int", inherit="no" ) ]
/**
 * <p>
 * SimpleOscilloscope
 * </p><p>
 * Based upon UIComponent and displays a sound waveform visualisation
 * based upon any currently playing sound under the control of this player.
 * </p><p>
 * Released under The MIT License by creative-cognition ltd<br/>
 * <a href="http://www.creative-cognition.co.uk/">http://www.creative-cognition.co.uk/</a><br/>
 * <a href="http://blog.creacog.co.uk/">http://blog.creacog.co.uk/</a>
 * </p>
 */
	public class SimpleOscilloscope extends UIComponent
	{
		//MINIMUM HEIGHT AND WIDTH
		public static const MIN_WIDTH	:uint = 10;
		public static const MIN_HEIGHT	:uint = 10;
		
		//STYLE NAMES
		public static const STYLE_LEFT_COLOUR		:String = "leftColour";
		public static const STYLE_RIGHT_COLOUR		:String = "rightColour";
		public static const STYLE_LEFT_ALPHA		:String = "leftAlpha";
		public static const STYLE_RIGHT_ALPHA		:String = "rightAlpha";
		public static const STYLE_WAVE_THICKNESS	:String = "waveThickness";
		public static const STYLE_WAVE_SEPARATION	:String = "waveSeparation";
		public static const STYLE_WAVE_DRAW_MODE	:String = "waveDrawMode";
		public static const STYLE_ALPHA_DECAY		:String = "alphaDecay";
		public static const STYLE_RED_MULTIPLIER	:String = "redMultiplier";
		public static const STYLE_GREEN_MULTIPLIER	:String = "greenMultiplier";
		public static const STYLE_BLUE_MULTIPLIER	:String = "blueMultiplier";
		public static const STYLE_BLUR_X			:String = "blurX";
		public static const STYLE_BLUR_Y			:String = "blurY";
		public static const STYLE_SCROLL_X			:String = "scrollX";
		public static const STYLE_SCROLL_Y			:String = "scrollY";
		
		//POSSIBLE STYLE_WAVE_DRAW_MODEs
		public static const WAVE_DRAW_LINE_ONLY		:String = WaveSprite.DRAW_LINE_ONLY;
		public static const WAVE_DRAW_FILLED		:String = WaveSprite.DRAW_FILLED;

		//STYLE DEFAULTS
		public static const DEFAULT_LEFT_COLOUR			:uint	= 0xFFFFFF; // white
		public static const DEFAULT_RIGHT_COLOUR		:uint	= 0xFF0000; // red
		public static const DEFAULT_LEFT_ALPHA			:Number	= 1.0;
		public static const DEFAULT_RIGHT_ALPHA			:Number	= 1.0;
		public static const DEFAULT_WAVE_THICKNESS		:uint	= 1;
		public static const DEFAULT_WAVE_SEPARATION		:uint	= 0.0;
		public static const DEFAULT_WAVE_DRAW_MODE		:String	= WAVE_DRAW_LINE_ONLY;
		public static const DEFAULT_ALPHA_DECAY			:Number = 0.0;
		public static const DEFAULT_RED_MULTIPLIER		:Number = 1.0;
		public static const DEFAULT_GREEN_MULTIPLIER	:Number = 1.0;
		public static const DEFAULT_BLUE_MULTIPLIER		:Number = 1.0;
		public static const DEFAULT_BLUR_X				:Number = 0.0;
		public static const DEFAULT_BLUR_Y				:Number = 0.0;
		public static const DEFAULT_BLUR_QUALITY		:int	= 3;
		public static const DEFAULT_SCROLL_X			:int	= 0;
		public static const DEFAULT_SCROLL_Y			:int	= 0;
		
		//STYLE CHANGED FLAGS - initialised to true so that initial styles get picked up
		private var alphaDirty			:Boolean = true;
		private var colourDirty			:Boolean = true;
		private var thicknessDirty		:Boolean = true;
		private var separationDirty		:Boolean = true;
		private var waveDrawModeDirty	:Boolean = true;
		private var matrixDirty			:Boolean = true;
		private var blurDirty			:Boolean = true;
		private var scrollDirty			:Boolean = true;

		// WE'LL RENDER THE SOUND WAVES INTO A COUPLE OF SPRITES
		private var leftChannelSprite	:WaveSprite;
		private var rightChannelSprite	:WaveSprite;
		private var waveContainerSprite	:Sprite;
		
		// WE'LL NEED TO PREVENT DRAWING OUTSIDE OUR BOUNDS RECT 
		private var maskSprite			:Sprite;
		
		private var soundData			:ByteArray;
		private var soundDataLeft		:ByteArray;
		private var soundDataRight		:ByteArray;

		// PROPERTIES TO CONTROL THE DECAY EFFECT		
		private var decayData			:BitmapData;
		private var decayBitmap			:Bitmap;
		private var colourMatrixArray	:Array;
		private var colourMatrixFilter	:ColorMatrixFilter;
		private var blurFilter			:BlurFilter;
		private var theScrollX			:int = DEFAULT_SCROLL_X;
		private var theScrollY			:int = DEFAULT_SCROLL_Y;
		
		//SOME FLAGS TO HELP US TO ONLY APPLY ACTIVE FILTERING
		private var blurActive			:Boolean = false;
		private var scrollActive		:Boolean = false;
		private var matrixActive		:Boolean = false;
		private var decayActive			:Boolean = false;
				
		public static const LOGGER:ILogger = Log.getLogger( "SimpleOscilloscope" );
		
		public function SimpleOscilloscope()
		{
			super();
		}
/**
 * part of UIComponent lifecycle
 * @see mx.core.UIComponent
 */		
		override public function initialize():void
		{
			super.initialize();
			
			soundData			= new ByteArray();
			soundDataLeft		= new ByteArray();
			soundDataRight		= new ByteArray();
			
			decayData			= new BitmapData( 1, 1, true, 0 );
			
			colourMatrixArray	= new Array( [	DEFAULT_RED_MULTIPLIER, 0, 0, 0, 0,
												0, DEFAULT_GREEN_MULTIPLIER, 0, 0, 0,
												0, 0, DEFAULT_BLUE_MULTIPLIER, 0, 0,
												0, 0, 0, DEFAULT_ALPHA_DECAY, 0 ] );
											
			colourMatrixFilter	= new ColorMatrixFilter( colourMatrixArray );
			
			blurFilter			= new BlurFilter(	DEFAULT_BLUR_X,
													DEFAULT_BLUR_Y,
													DEFAULT_BLUR_QUALITY );		
		}
/**
 * part of UIComponent lifecycle
 * @see mx.core.UIComponent
 */			
		override protected function createChildren():void
		{
			super.createChildren();

			if( decayBitmap == null ) decayBitmap	= new Bitmap();
			
			addChild( decayBitmap );
			
			if( leftChannelSprite	== null ) leftChannelSprite		= new WaveSprite;
			if( rightChannelSprite	== null ) rightChannelSprite	= new WaveSprite;
			
			leftChannelSprite.waveColour	= DEFAULT_LEFT_COLOUR;
			rightChannelSprite.waveColour	= DEFAULT_RIGHT_COLOUR;
			
			if( waveContainerSprite	== null ) waveContainerSprite	= new Sprite();
			
			waveContainerSprite.addChild( leftChannelSprite );
			waveContainerSprite.addChild( rightChannelSprite );
			
			addChild( waveContainerSprite );
			
			if( maskSprite == null ) maskSprite = new Sprite();
			
			addChild( maskSprite );
			
			mask = maskSprite;
			
			addEventListener(	Event.ADDED_TO_STAGE,
								handleAddedToStage,
								false,
								0,
								true );
								
			addEventListener(	Event.REMOVED_FROM_STAGE,
								handleRemovedFromStage,
								false,
								0,
								true );
		}

/**
 * part of UIComponent - put into effect anything that changes this component's appearance
 * @see mx.core.UIComponent
 */			
		override protected function updateDisplayList(	unscaledWidth	:Number,
														unscaledHeight	:Number ):void
		{
			LOGGER.info( "updateDisplayList" );
			
			super.updateDisplayList(	unscaledWidth,
										unscaledHeight );
				
			leftChannelSprite.width		= unscaledWidth;
			leftChannelSprite.height	= unscaledHeight; 

			rightChannelSprite.width	= unscaledWidth;
			rightChannelSprite.height	= unscaledHeight;
			
			decayData = new BitmapData(	( unscaledWidth  == 0 ? 1 : unscaledWidth ),
										( unscaledHeight == 0 ? 1 : unscaledHeight ),
										true,
										0 );
						
			decayData.draw( decayBitmap );
							
			decayBitmap.bitmapData = decayData;
												
			maskSprite.graphics.clear();
			maskSprite.graphics.beginFill( 0x000000, 1);
			maskSprite.graphics.drawRect( 0, 0, unscaledWidth, unscaledHeight );
			maskSprite.graphics.endFill();
			
			if( separationDirty )
			{
				var separation:uint = 
					( getStyle( STYLE_WAVE_SEPARATION ) == undefined ? DEFAULT_WAVE_SEPARATION : getStyle( STYLE_WAVE_SEPARATION ) );
					
				leftChannelSprite.y = - separation;
				
				rightChannelSprite.y = separation;
				
				separationDirty = false;
			}
			
			if( alphaDirty )
			{
				leftChannelSprite.alpha =
					( getStyle( STYLE_LEFT_ALPHA ) == undefined ? DEFAULT_LEFT_ALPHA : getStyle( STYLE_LEFT_ALPHA ) );

				rightChannelSprite.alpha =
					( getStyle( STYLE_RIGHT_ALPHA ) == undefined ? DEFAULT_RIGHT_ALPHA : getStyle( STYLE_RIGHT_ALPHA ) );
					
				alphaDirty = false;
			}
			
			if( colourDirty )
			{
				leftChannelSprite.waveColour =
					( getStyle( STYLE_LEFT_COLOUR ) == undefined ? DEFAULT_LEFT_COLOUR : getStyle( STYLE_LEFT_COLOUR ) );
				
				rightChannelSprite.waveColour =
					( getStyle( STYLE_RIGHT_COLOUR ) == undefined ? DEFAULT_RIGHT_COLOUR : getStyle( STYLE_RIGHT_COLOUR ) );

				colourDirty = false;
			}
			
			if( thicknessDirty )
			{
				rightChannelSprite.waveThickness =
				leftChannelSprite.waveThickness  =
					( getStyle( STYLE_WAVE_THICKNESS ) == undefined ? DEFAULT_WAVE_THICKNESS : getStyle( STYLE_WAVE_THICKNESS ) );
				
				thicknessDirty = false;
			}
			
			if( waveDrawModeDirty )
			{
				rightChannelSprite.drawMode =
				leftChannelSprite.drawMode =
					( getStyle( STYLE_WAVE_DRAW_MODE ) == undefined ? DEFAULT_WAVE_DRAW_MODE : getStyle( STYLE_WAVE_DRAW_MODE ) );
					
				waveDrawModeDirty = false;
			}
			
			if( matrixDirty )
			{
				var alphaMultiplier:Number = 
					( getStyle( STYLE_ALPHA_DECAY ) == undefined ? DEFAULT_ALPHA_DECAY : getStyle( STYLE_ALPHA_DECAY ) );
				
				decayActive = ( alphaMultiplier != 0.0 );
				
				if( decayActive )
				{
					// WE ONLY NEED SORT THE MATRIX IF THERE IS DECAY IN EFFECT
					var redMultiplierValue	:Number = 
						( getStyle( STYLE_RED_MULTIPLIER ) == undefined ? DEFAULT_RED_MULTIPLIER : getStyle( STYLE_RED_MULTIPLIER ) );

					var greenMultiplierValue	:Number = 
						( getStyle( STYLE_GREEN_MULTIPLIER ) == undefined ? DEFAULT_GREEN_MULTIPLIER : getStyle( STYLE_GREEN_MULTIPLIER ) );

					var blueMultiplierValue	:Number = 
						( getStyle( STYLE_BLUE_MULTIPLIER ) == undefined ? DEFAULT_BLUE_MULTIPLIER : getStyle( STYLE_BLUE_MULTIPLIER ) );
				
					updateColourMatrix(	redMultiplierValue,
										greenMultiplierValue,
										blueMultiplierValue,
										alphaMultiplier	);
				}
				matrixDirty = false;
			}
			
			if( blurDirty )
			{
				var newBlurX :Number = 
					( getStyle( STYLE_BLUR_X ) == undefined ? DEFAULT_BLUR_X : getStyle( STYLE_BLUR_X ) );

				var newBlurY :Number = 
					( getStyle( STYLE_BLUR_Y ) == undefined ? DEFAULT_BLUR_Y : getStyle( STYLE_BLUR_Y ) );
					
				blurActive = ( newBlurX > 0 || newBlurY > 0 );
				
				if( blurActive )
				{
					blurFilter.blurX = newBlurX;
					blurFilter.blurY = newBlurY;
				}
				blurDirty = false;
			}
			
			if( scrollDirty )
			{
				theScrollX = ( getStyle( STYLE_SCROLL_X ) == undefined ? DEFAULT_SCROLL_X : getStyle( STYLE_SCROLL_X ) );
				theScrollY = ( getStyle( STYLE_SCROLL_Y ) == undefined ? DEFAULT_SCROLL_Y : getStyle( STYLE_SCROLL_Y ) );
				
				scrollActive = ( theScrollX != 0 || theScrollY != 0 );
				
				scrollDirty = false;
			}
		}
/**
 * record changed style changes to
 * update next time we updateDisplayList
 * @see mx.core.UIComponent
 */			
		override public function styleChanged( styleProp :String ):void
		{
			LOGGER.info( "styleChanged : " + styleProp );
			
			super.styleChanged( styleProp );
			
			switch( styleProp )
			{
				case STYLE_LEFT_ALPHA  :
				case STYLE_RIGHT_ALPHA :
				{
					alphaDirty = true;
					
					invalidateDisplayList();
					
					break;
				}
				case STYLE_LEFT_COLOUR  :
				case STYLE_RIGHT_COLOUR :	
				{
					colourDirty = true;
					
					invalidateDisplayList();
					
					break;
				}
				case STYLE_WAVE_THICKNESS :
				{
					thicknessDirty = true;
					
					invalidateDisplayList();
					
					break;
				}
				case STYLE_WAVE_SEPARATION :
				{
					separationDirty = true;
					
					invalidateDisplayList();
					
					break;
				}
				case STYLE_WAVE_DRAW_MODE :
				{
					waveDrawModeDirty = true;
					
					invalidateDisplayList();
					
					break;
				}
				case STYLE_ALPHA_DECAY :
				case STYLE_RED_MULTIPLIER :
				case STYLE_GREEN_MULTIPLIER :
				case STYLE_BLUE_MULTIPLIER :
				{
					matrixDirty = true;
					
					invalidateDisplayList();
					
					break;
				}
				case STYLE_BLUR_X :
				case STYLE_BLUR_Y :
				{
					blurDirty = true;
					
					invalidateDisplayList();
					
					break;
				}
				case STYLE_SCROLL_X :
				case STYLE_SCROLL_Y :
				{
					scrollDirty = true;
					
					invalidateDisplayList();
					
					break;
				}
			}
		}
/**
 * @private
 * While on stage, we'll listen for Event.ENTER_FRAME 
 */
		private function handleAddedToStage( e:Event ):void
		{
			addEventListener(	Event.ENTER_FRAME,
								handleEnterFrame );
		}
/**
 * @private
 * When off stage, we nolonger need to listen for Event.ENTER_FRAME
 */
		private function handleRemovedFromStage( e:Event ):void
		{
			removeEventListener(	Event.ENTER_FRAME,
									handleEnterFrame );
		}
/**
 * @private
 * update the vizualisation every frame, only if visible
 */		
		private function handleEnterFrame( e:Event ):void
		{
			if( visible )
			{
				SoundMixer.computeSpectrum( soundData );
				
				soundData.readBytes( soundDataLeft,  0, 1024 );
				soundData.readBytes( soundDataRight, 0 );
				
				leftChannelSprite.drawWave(  soundDataLeft );	
				rightChannelSprite.drawWave( soundDataRight );
				
				if( decayActive )
				{
					decayData.draw( waveContainerSprite );
					
					if( scrollActive ) decayData.scroll( theScrollX, theScrollY );
					
					if( blurActive )
					{
						decayData.applyFilter(	decayData,
												decayData.rect,
												new Point(0,0),
												blurFilter );
					}
					decayData.applyFilter(	decayData,
											decayData.rect,
											new Point(0,0),
											colourMatrixFilter );
				}
			}
		}
/**
 * @private
 * utility function to simplify calls to update the colour matrix array
 */
		private function updateColourMatrix(	redMultiplier	:Number = DEFAULT_RED_MULTIPLIER,
												greenMultiplier	:Number = DEFAULT_GREEN_MULTIPLIER,
												blueMultiplier	:Number = DEFAULT_BLUE_MULTIPLIER,
												alphaMultiplier	:Number = DEFAULT_ALPHA_DECAY ):void
		{
			var theArray:Array = colourMatrixFilter.matrix;
			
			theArray[ 0 ]	= redMultiplier;
			theArray[ 6 ]	= greenMultiplier;
			theArray[ 12 ]	= blueMultiplier;
			theArray[ 18 ]	= alphaMultiplier;
			
			colourMatrixFilter.matrix = theArray;
		}
	}
}